/***********************************************
 This file is part of redia-library-app-ios.
 
 Copyright (c) 2012, 2013 Redia A/S
 
 Redia-library-app-ios is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 Redia-library-app-ios is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with redia-library-app-ios.  If not, see <http://www.gnu.org/licenses/>.
 
 *********************************************** */


#import "InfoObject.h"
#import "defines.h"
#import "NSString+LibUtils.h"
#import "InfoVoteOption.h"

@implementation InfoObject

@synthesize translatedInfoObjects;
@synthesize xmlData;
@synthesize infoObjectId;

/*
- (id)initWithUrl:(NSString *)url {
	self = [super init];
	
    
	self.xmlData = [[NSString alloc] initWithContentsOfURL:[NSURL URLWithString:url] encoding:NSUTF8StringEncoding error:nil];
    if (self.xmlData==nil) {
        [NSException raise:@"No data received" format:@"Infoobject initWithUrl called but error in server response"];
    }
    if ([self.xmlData length]==0) {
        [NSException raise:@"Empty string not allowed" format:@"Infoobject initWithUrl called but error in server response"];
    }

	parserState = PARSER_STATE_UNPARSED;
	infoObjectType = INFOOBJECT_TYPE_UNKNOWN;
	infoObjectId = @"";

	self.translatedInfoObjects = [[NSMutableDictionary alloc] init];

	return self;
	
}
*/

- (id)initWithXmlString:(NSString *)d
{
	self = [super init];
    if (d==nil) {
        [NSException raise:@"Null string not allowed" format:@"Infoobject initWithXmlString called with null parameter"];
    }
    if ([d length]==0) {
        [NSException raise:@"Empty string not allowed" format:@"Infoobject initWithXmlString called with empty string parameter"];
    }
	self.xmlData = d;
	parserState = PARSER_STATE_UNPARSED;
	infoObjectType = INFOOBJECT_TYPE_UNKNOWN;
	infoObjectId = @"";

	self.translatedInfoObjects = [[NSMutableDictionary alloc] init];
	return self;
}

- (void)parse
{
    NSError *error;
    TBXML *p = [TBXML newTBXMLWithXMLString:self.xmlData error:&error];
    if (error) {
        [NSException raise:@"Invalid XML string given." format:@"Infoobject parse called with invalid data in xmlData member."];
    }
    
	TBXMLElement *protocolElement = [TBXML childElementNamed:@"protocol" parentElement:p.rootXMLElement];
	if (protocolElement!=0) {
		protocol = [[TBXML textForElement:protocolElement] intValue];
	}
	
    TBXMLElement *elementElement = [TBXML childElementNamed:@"element" parentElement:p.rootXMLElement];
    TBXMLElement *calendarelementElement = [TBXML childElementNamed:@"calendarelement" parentElement:p.rootXMLElement];
    TBXMLElement *voteelementElement = [TBXML childElementNamed:@"voteelement" parentElement:p.rootXMLElement];
    
	if (elementElement!=0) {
		while (elementElement!=0) {
			// Handle element-nodes
			infoObjectType = INFOOBJECT_TYPE_STANDARD;
			// Determine language
			NSString *lang = [TBXML valueOfAttributeNamed:@"language" forElement:elementElement];
			if ([lang length]>0) {
				//dep: self->language = lang;
				InfoObject3 *io = [[InfoObject3 alloc] init];
				[translatedInfoObjects setValue:io forKey:lang];
                [self handleVersion3InfoObject:elementElement infoObject3:io language:lang];
                parserState = PARSER_STATE_PARSE_SUCCESS;
			} else {
				DLog(@"No language for element. Skipping translation.");
			}
			elementElement = elementElement->nextSibling;
		}
	} else if (calendarelementElement != 0) {
        while (calendarelementElement!=0) {
            // Handle element-nodes
            infoObjectType = INFOOBJECT_TYPE_ARRANGEMENT;
            // Determine language
            NSString *lang = [TBXML valueOfAttributeNamed:@"language" forElement:calendarelementElement];
            if ([lang length]>0) {
                //dep: self->language = lang;
                InfoObject3 *io = [[InfoObject3 alloc] init];
                [translatedInfoObjects setValue:io forKey:lang];
                [self handleVersion3InfoObject:calendarelementElement infoObject3:io language:lang];
                parserState = PARSER_STATE_PARSE_SUCCESS;
            } else {
                DLog(@"No language for element. Skipping translation.");
            }
            calendarelementElement = calendarelementElement->nextSibling;
        }
    } else if (voteelementElement != 0) {
        while (voteelementElement != 0) {
            // Handle element-nodes
            infoObjectType = INFOOBJECT_TYPE_VOTE;
            // Determine language
            NSString *lang = [TBXML valueOfAttributeNamed:@"language" forElement:voteelementElement];
            if ([lang length]>0) {
                //dep: self->language = lang;
                InfoObject3 *io = [[InfoObject3 alloc] init];
                [translatedInfoObjects setValue:io forKey:lang];
                [self handleVersion3InfoObject:voteelementElement infoObject3:io language:lang];
                parserState = PARSER_STATE_PARSE_SUCCESS;
            } else {
                DLog(@"No language for element. Skipping translation.");
            }
            voteelementElement = voteelementElement->nextSibling;
        }
    } else {
        NSLog(@"%@",xmlData);
        @throw [NSException exceptionWithName:@"ParseError" reason:@"Failed to find standard element node under root node." userInfo:nil];
    }
	
	self.xmlData=nil;
}

- (NSString *)getId
{
	if (!(parserState == PARSER_STATE_UNPARSED_WITH_ID || parserState == PARSER_STATE_PARSE_SUCCESS)) {
		[NSException raise:@"getId not allowed on parsed infoObject" format:@"Tried to call getId on an unparsed infoObject with parserstate %d",parserState];
	}
	return infoObjectId;
	
	/* old code
	NSString* anylang = [[self getLanguages] lastObject];
	return [[self.translatedInfoObjects objectForKey:anylang] infoObjectId];
	 */
}

- (void)setId:(NSString*)anObjectId
{
	if (parserState != PARSER_STATE_UNPARSED) {
		[NSException raise:@"setId not allowed on a parsed infoObject" 
					format:@"Tried to call setId on a parsed infoObject with parserstate %d",parserState];
	}
	infoObjectId = anObjectId;
	parserState = PARSER_STATE_UNPARSED_WITH_ID;
}

- (NSArray *)getLanguages
{
	return [self.translatedInfoObjects allKeys];
}

- (NSString *)getTemplate:(NSString *)lang
{
	if ([self.translatedInfoObjects objectForKey:lang]) {
		InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:lang];
		return io.templateId;
	} else {
		return nil;
	}
}

- (void)handleVersion3InfoObject:(TBXMLElement *)elementElement infoObject3:(InfoObject3 *)infoObject3 language:(NSString *)language
{
	// id
	// duration
	// headline
	// subheadline
	// body
	// template
	//  id
	//  extra
	// channels
	// urls
	// source
	// editor
	// category
	// tags
	// multimedia
	// elements
	// comments
	// geolocations
	// extra
	
	TBXMLElement *idElement = [TBXML childElementNamed:@"id" parentElement:elementElement];	
	TBXMLElement *durationElement = [TBXML childElementNamed:@"duration" parentElement:elementElement];
	TBXMLElement *headlineElement = [TBXML childElementNamed:@"headline" parentElement:elementElement];
	TBXMLElement *subheadlineElement = [TBXML childElementNamed:@"subheadline" parentElement:elementElement];
	TBXMLElement *bodyElement = [TBXML childElementNamed:@"body" parentElement:elementElement];
	TBXMLElement *templateElement = [TBXML childElementNamed:@"template" parentElement:elementElement];
	TBXMLElement *channelsElement = [TBXML childElementNamed:@"channels" parentElement:elementElement];
	TBXMLElement *urlsElement = [TBXML childElementNamed:@"urls" parentElement:elementElement];
	TBXMLElement *sourceElement = [TBXML childElementNamed:@"source" parentElement:elementElement];
	TBXMLElement *editorElement = [TBXML childElementNamed:@"editor" parentElement:elementElement];
	TBXMLElement *categoryElement = [TBXML childElementNamed:@"category" parentElement:elementElement];
	TBXMLElement *metaDataElement = [TBXML childElementNamed:@"extra" parentElement:elementElement];
	TBXMLElement *tagsElement = [TBXML childElementNamed:@"tags" parentElement:elementElement];
	TBXMLElement *multimediaElement = [TBXML childElementNamed:@"multimedia" parentElement:elementElement];
	TBXMLElement *elementsElement = [TBXML childElementNamed:@"elements" parentElement:elementElement];
	TBXMLElement *commentsElement = [TBXML childElementNamed:@"comments" parentElement:elementElement];
	TBXMLElement *geolocationsElement = [TBXML childElementNamed:@"geolocations" parentElement:elementElement];
	
	TBXMLElement *beginElement = nil;
	TBXMLElement *endElement = nil;
	TBXMLElement *locationsElement = nil;
	if (infoObjectType==INFOOBJECT_TYPE_ARRANGEMENT) {
		beginElement = [TBXML childElementNamed:@"begin" parentElement:elementElement];
		endElement = [TBXML childElementNamed:@"end" parentElement:elementElement];
		locationsElement = [TBXML childElementNamed:@"locations" parentElement:elementElement];
		
		if (!beginElement) {
            @throw [NSException exceptionWithName:@"ParseError" reason:@"No begin node in calendarelement" userInfo:nil];
		}
		if (!endElement) {
            @throw [NSException exceptionWithName:@"ParseError" reason:@"No end node in calendarelement" userInfo:nil];
		}
		if (!locationsElement) {
            @throw [NSException exceptionWithName:@"ParseError" reason:@"No location node in calendarelement" userInfo:nil];
		}
	}
	
    TBXMLElement *optionsElement = nil;
    if (infoObjectType == INFOOBJECT_TYPE_VOTE) {
        optionsElement = [TBXML childElementNamed:@"options" parentElement:elementElement];
        
        if (!optionsElement) {
            @throw [NSException exceptionWithName:@"ParseError" reason:@"No options node in voteelement" userInfo:nil];
        }
    }
    
	if (!categoryElement) {
        @throw [NSException exceptionWithName:@"ParseError" reason:@"No category node in element" userInfo:nil];
	}
	
	
	if (!metaDataElement) {
        @throw [NSException exceptionWithName:@"ParseError" reason:@"No metadata node in element" userInfo:nil];
	}
	
	if (!tagsElement) {
        @throw [NSException exceptionWithName:@"ParseError" reason:@"No tags node in element" userInfo:nil];
	}
	
	if (!multimediaElement) {
        @throw [NSException exceptionWithName:@"ParseError" reason:@"No multimedia node in element" userInfo:nil];
	}
	
	if (!elementsElement) {
        @throw [NSException exceptionWithName:@"ParseError" reason:@"No elements node in element" userInfo:nil];
	}
	
	if (!commentsElement) {
        @throw [NSException exceptionWithName:@"ParseError" reason:@"No comments node in element" userInfo:nil];
	}
	
	if (!geolocationsElement) {
        @throw [NSException exceptionWithName:@"ParseError" reason:@"No geolocations node in element" userInfo:nil];
	}
	
	if (!sourceElement) {
        @throw [NSException exceptionWithName:@"ParseError" reason:@"No source node in element" userInfo:nil];
	}
	
	if (!editorElement) {
        @throw [NSException exceptionWithName:@"ParseError" reason:@"No editor node in element" userInfo:nil];
	}
	
	if (!templateElement) {
        @throw [NSException exceptionWithName:@"ParseError" reason:@"No template node in element" userInfo:nil];
	}
	
	if (!channelsElement) {
        @throw [NSException exceptionWithName:@"ParseError" reason:@"No channels node in element" userInfo:nil];
	}
	
	if (!urlsElement) {
        @throw [NSException exceptionWithName:@"ParseError" reason:@"No urls node in element" userInfo:nil];
	}
	
	if (!idElement) {
        @throw [NSException exceptionWithName:@"ParseError" reason:@"No id node in element" userInfo:nil];
	}
	
	if (!durationElement) {
        @throw [NSException exceptionWithName:@"ParseError" reason:@"No duration node in element" userInfo:nil];
	}
	
	if (!headlineElement) {
        @throw [NSException exceptionWithName:@"ParseError" reason:@"No headline node in element" userInfo:nil];
	}
	
	if (!subheadlineElement) {
        @throw [NSException exceptionWithName:@"ParseError" reason:@"No subheadline node in element" userInfo:nil];
	}
	
	if (infoObjectType != INFOOBJECT_TYPE_VOTE && !bodyElement) {
        @throw [NSException exceptionWithName:@"ParseError" reason:@"No body node in element" userInfo:nil];
	}
	
	//DLog(@"All top-level fields validated");
	
	infoObject3.language = language;
	
	infoObject3.headline = [[TBXML textForElement:headlineElement] unescapedFromXML];

	if (infoObjectType != INFOOBJECT_TYPE_VOTE) {
        infoObject3.body = [[TBXML textForElement:bodyElement] unescapedFromXML];
    }
    else {
        infoObject3.body = @"";
    }
	
	//[escaped_body release];
	
	
	infoObject3.subHeadline = [[TBXML textForElement:subheadlineElement] unescapedFromXML];
	
	infoObject3.infoObjectId =  [TBXML textForElement:idElement];
	self.infoObjectId = infoObject3.infoObjectId; //replicate into self.infoObjectID
	
	infoObject3.duration = [[TBXML textForElement:durationElement] intValue];
	infoObject3.templateId = [TBXML textForElement:[TBXML childElementNamed:@"id" parentElement:templateElement]];
	infoObject3.category = [TBXML textForElement:categoryElement];
	
	// Comments
	TBXMLElement *commentElement = [TBXML childElementNamed:@"comment" parentElement:commentsElement];
	NSMutableArray *comments = [[NSMutableArray alloc] init];
	while (commentElement) {
		InfoComment *comment = [[InfoComment alloc] init];
		comment.author = [[TBXML textForElement:[TBXML childElementNamed:@"author" parentElement:commentElement]] unescapedFromXML];
		comment.body = [[TBXML textForElement:[TBXML childElementNamed:@"body" parentElement:commentElement]] unescapedFromXML];
		comment.date = [TBXML textForElement:[TBXML childElementNamed:@"date" parentElement:commentElement]];
		//DLog(@"Found comment from %@", comment.author);
		[comments addObject:comment];
		commentElement = commentElement->nextSibling;
	}
	infoObject3.comments = comments;
	
	// Channels
	TBXMLElement *channelElement = [TBXML childElementNamed:@"channel" parentElement:channelsElement];
	NSMutableArray *channels = [[NSMutableArray alloc] init];
    NSMutableDictionary *channelIDs = [[NSMutableDictionary alloc] init];
	while (channelElement) {
		TBXMLElement *nameElement = [TBXML childElementNamed:@"name" parentElement:channelElement];
		[channels addObject:[TBXML textForElement:nameElement]];
	
        TBXMLElement *channelIDElement = [TBXML childElementNamed:@"channelid" parentElement:channelElement];
        [channelIDs setObject:[TBXML textForElement:nameElement] forKey:[TBXML textForElement:channelIDElement]];
        
        //DLog(@"Found channel named %@", [TBXML textForElement:nameElement]);
		channelElement = channelElement->nextSibling;
	}
	infoObject3.channels = channels;
    infoObject3.channelIDs = [NSDictionary dictionaryWithDictionary:channelIDs];
	
	// Urls
	TBXMLElement *urlElement = [TBXML childElementNamed:@"url" parentElement:urlsElement];
	NSMutableArray *urls = [[NSMutableArray alloc] init];
	while (urlElement) {
		TBXMLElement *hrefElement = [TBXML childElementNamed:@"href" parentElement:urlElement];
		[urls addObject:[[TBXML textForElement:hrefElement] unescapedFromXML]];
		//DLog(@"Found url %@", [TBXML textForElement:hrefElement]);
		urlElement = urlElement->nextSibling;
	}
	infoObject3.urls = urls;
	
	// Source
	InfoSource *source = [[InfoSource alloc] init];
	source.name = [[TBXML textForElement:[TBXML childElementNamed:@"name" parentElement:sourceElement]] unescapedFromXML];
	source.email = [TBXML textForElement:[TBXML childElementNamed:@"email" parentElement:sourceElement]];
	source.organisation = [[TBXML textForElement:[TBXML childElementNamed:@"organisation" parentElement:sourceElement]] unescapedFromXML];
	source.logo = [TBXML textForElement:[TBXML childElementNamed:@"logo" parentElement:sourceElement]];
	infoObject3.source = source;
	// Editor
	InfoEditor *editor = [[InfoEditor alloc] init];
	editor.name = [[TBXML textForElement:[TBXML childElementNamed:@"name" parentElement:editorElement]] unescapedFromXML];
	editor.email = [TBXML textForElement:[TBXML childElementNamed:@"email" parentElement:editorElement]];
	editor.organisation = [[TBXML textForElement:[TBXML childElementNamed:@"organisation" parentElement:editorElement]] unescapedFromXML];
	infoObject3.editor = editor;
	
	// MetaData
	TBXMLElement *metaDataItemElement = [TBXML childElementNamed:@"item" parentElement:metaDataElement];
	NSMutableDictionary *metadata = [[NSMutableDictionary alloc] init];
	while (metaDataItemElement) {
		TBXMLElement *keyElement = [TBXML childElementNamed:@"key" parentElement:metaDataItemElement];
		TBXMLElement *valueElement = [TBXML childElementNamed:@"value" parentElement:metaDataItemElement];
		[metadata setObject:[[TBXML textForElement:valueElement] unescapedFromXML] forKey:[TBXML textForElement:keyElement]];
		metaDataItemElement = metaDataItemElement->nextSibling;
	}
	infoObject3.metaData = metadata;

	
	// Tags
	TBXMLElement *tagElement = [TBXML childElementNamed:@"tag" parentElement:tagsElement];
	NSMutableArray *tags = [[NSMutableArray alloc] init];
	while (tagElement) {
		[tags addObject:[TBXML textForElement:tagElement]];
		tagElement = tagElement->nextSibling;
	}
	infoObject3.tags = tags;
	
	// Media
	TBXMLElement *mediaElement = [TBXML childElementNamed:@"media" parentElement:multimediaElement];
	NSMutableArray *multiMedia = [[NSMutableArray alloc] init];
	while (mediaElement) {
		InfoMedia *im = [[InfoMedia alloc] init];

        TBXMLElement *element = [TBXML childElementNamed:@"id" parentElement:mediaElement];
        if (!element) {
            @throw [NSException exceptionWithName:@"ParseError" reason:@"No id in media object." userInfo:nil];
        }
		im.infoMediaId = [TBXML textForElement:element];
        
        element = [TBXML childElementNamed:@"type" parentElement:mediaElement];
        if (!element) {
            @throw [NSException exceptionWithName:@"ParseError" reason:@"No type in media object." userInfo:nil];
        }
		im.mediaType = [TBXML textForElement:element];
        
        element = [TBXML childElementNamed:@"title" parentElement:mediaElement];
        if (!element) {
            @throw [NSException exceptionWithName:@"ParseError" reason:@"No title in media object." userInfo:nil];
        }    
		im.title = [[TBXML textForElement:element] unescapedFromXML];
        
        element = [TBXML childElementNamed:@"artist" parentElement:mediaElement];
        if (!element) {
            @throw [NSException exceptionWithName:@"ParseError" reason:@"No artist in media object." userInfo:nil];
        }
		im.artist = [[TBXML textForElement:element] unescapedFromXML];
        
        element = [TBXML childElementNamed:@"year" parentElement:mediaElement];
        if (!element) {
            @throw [NSException exceptionWithName:@"ParseError" reason:@"No year in media object." userInfo:nil];
        }
		im.year = [[TBXML textForElement:element] unescapedFromXML];
        
        element = [TBXML childElementNamed:@"inthepicture" parentElement:mediaElement];
        if (!element) {
            @throw [NSException exceptionWithName:@"ParseError" reason:@"No inthepicture in media object." userInfo:nil];
        }
		im.inThePicture = [[TBXML textForElement:element] unescapedFromXML];
        
        element = [TBXML childElementNamed:@"source" parentElement:mediaElement];
        if (!element) {
            @throw [NSException exceptionWithName:@"ParseError" reason:@"No source in media object." userInfo:nil];
        }
		im.source = [TBXML textForElement:element];
        
        element = [TBXML childElementNamed:@"description" parentElement:mediaElement];
        if (!element) {
            @throw [NSException exceptionWithName:@"ParseError" reason:@"No description in media object." userInfo:nil];
        }
		im.description = [TBXML textForElement:element];
        
        TBXMLElement* mediaUrlsElement = [TBXML childElementNamed:@"urls" parentElement:mediaElement];
        if (mediaUrlsElement) {
            //not all media types has urls (only in image and video)
            im.sourceURL = [TBXML textForElement:[TBXML childElementNamed:@"source" parentElement:mediaUrlsElement]];
            im.thumbURL = [TBXML textForElement:[TBXML childElementNamed:@"thumb" parentElement:mediaUrlsElement]];
        } else {
            //other media types (audio) has a single url element
            im.sourceURL = [TBXML textForElement:[TBXML childElementNamed:@"url" parentElement:mediaElement]];
        }
		
        TBXMLElement *durationtext = [TBXML childElementNamed:@"displayduration" parentElement:mediaElement];
		if (durationtext) {
			//may not be present in some xlm responses
			im.displayDuration = [TBXML textForElement:durationtext];
		}
        
        // Encodings
        
        TBXMLElement *encodingsElement = [TBXML childElementNamed:@"encodings" parentElement:mediaElement];
        if (encodingsElement) {
            TBXMLElement *encodingElement = [TBXML childElementNamed:@"encoding" parentElement:encodingsElement];
            NSMutableArray *encodings = [[NSMutableArray alloc] init];
            while (encodingElement) {
                InfoMediaEncoding *ime  = [[InfoMediaEncoding alloc] init];
                
                element = [TBXML childElementNamed:@"url" parentElement:encodingElement];
                if (!element) {
                    @throw [NSException exceptionWithName:@"ParseError" reason:@"No url in encoding object." userInfo:nil];
                }
                ime.url = [[TBXML textForElement:element] unescapedFromXML];
                
                element = [TBXML childElementNamed:@"container" parentElement:encodingElement];
                if (!element) {
                    @throw [NSException exceptionWithName:@"ParseError" reason:@"No container in encoding object." userInfo:nil];
                }
                ime.container = [[TBXML textForElement:element] unescapedFromXML];
                
                element = [TBXML childElementNamed:@"videostreamencoding" parentElement:encodingElement];
                if (!element) {
                    @throw [NSException exceptionWithName:@"ParseError" reason:@"No videostreamencoding in encoding object." userInfo:nil];
                }
                ime.videostreamencoding = [[TBXML textForElement:element] unescapedFromXML];
                
                element = [TBXML childElementNamed:@"audiostreamencoding" parentElement:encodingElement];
                if (!element) {
                    @throw [NSException exceptionWithName:@"ParseError" reason:@"No audiostreamencoding in encoding object." userInfo:nil];
                }
                ime.audiostreamencoding = [[TBXML textForElement:element] unescapedFromXML];
                
                element = [TBXML childElementNamed:@"bitrate" parentElement:encodingElement];
                if (!element) {
                    @throw [NSException exceptionWithName:@"ParseError" reason:@"No bitrate in encoding object." userInfo:nil];
                }
                ime.bitrate = [[TBXML textForElement:element] unescapedFromXML];
                
                element = [TBXML childElementNamed:@"framerate" parentElement:encodingElement];
                if (!element) {
                    @throw [NSException exceptionWithName:@"ParseError" reason:@"No framerate in encoding object." userInfo:nil];
                }
                ime.framerate = [[TBXML textForElement:element] unescapedFromXML];
                
                element = [TBXML childElementNamed:@"videoresolution" parentElement:encodingElement];
                if (!element) {
                    @throw [NSException exceptionWithName:@"ParseError" reason:@"No videoresolution in encoding object." userInfo:nil];
                }
                ime.videoresolution = [[TBXML textForElement:element] unescapedFromXML];
                
                TBXMLElement *devicesElement = [TBXML childElementNamed:@"devices" parentElement:encodingElement];
                if (devicesElement) {
                    TBXMLElement *deviceElement = [TBXML childElementNamed:@"device" parentElement:devicesElement];
                    while (deviceElement) {
                        NSString *deviceString = [[TBXML textForElement:deviceElement] unescapedFromXML];
                        if ([deviceString caseInsensitiveCompare:@"tablet_ipad"] == NSOrderedSame) {
                            ime.supportedDevices = ime.supportedDevices | TABLET_IPAD;
                            im.ipadEncoding = ime;
                        }
                        else if ([deviceString caseInsensitiveCompare:@"tablet_android"] == NSOrderedSame) {
                            ime.supportedDevices = ime.supportedDevices | TABLET_ANDROID;
                        }
                        else if ([deviceString caseInsensitiveCompare:@"phone_iphone"] == NSOrderedSame) {
                            ime.supportedDevices = ime.supportedDevices | PHONE_IPHONE;
                            im.iphoneEncoding = ime;
                        }
                        else if ([deviceString caseInsensitiveCompare:@"phone_android"] == NSOrderedSame) {
                            ime.supportedDevices = ime.supportedDevices | PHONE_ANDROID;
                        }
                        else if ([deviceString caseInsensitiveCompare:@"desktop_linux"] == NSOrderedSame) {
                            ime.supportedDevices = ime.supportedDevices | DESKTOP_LINUX;
                        }
                        else if ([deviceString caseInsensitiveCompare:@"desktop_macos"] == NSOrderedSame) {
                            ime.supportedDevices = ime.supportedDevices | DESKTOP_MACOS;
                        }
                        else if ([deviceString caseInsensitiveCompare:@"desktop_windows"] == NSOrderedSame) {
                            ime.supportedDevices = ime.supportedDevices | DESKTOP_WINDOWS;
                        }
                        else if ([deviceString caseInsensitiveCompare:@"original"] == NSOrderedSame) {
                            ime.supportedDevices = ime.supportedDevices | ORIGINAL;
                        }
                        deviceElement = deviceElement->nextSibling;
                    }
                }
                
                //DLog(@"Successfully decoded geolocation %@", igl.name);
                [encodings addObject:ime];
                encodingElement = encodingElement->nextSibling;
            }
            im.mediaEncodings = encodings;
        }

        
		[multiMedia addObject:im];
		//DLog(@"Found media with id %@", im.infoMediaId);
		mediaElement = mediaElement->nextSibling;
	}
	infoObject3.multimedia = multiMedia;
	
	// Elements
	TBXMLElement *elementRefElement = [TBXML childElementNamed:@"elementref" parentElement:elementsElement];
	NSMutableArray *elements = [[NSMutableArray alloc] init];
	while (elementRefElement) {
		[elements addObject:[TBXML textForElement:elementRefElement]];
		 elementRefElement = elementRefElement->nextSibling;
	}
	infoObject3.childInfoObjects = elements;
	
	// Geolocations
	TBXMLElement *geoLocationElement = [TBXML childElementNamed:@"geolocation" parentElement:geolocationsElement];
	NSMutableArray *geoLocations = [[NSMutableArray alloc] init];
	TBXMLElement *element;
    while (geoLocationElement) {
		InfoGeoLocation *igl  = [[InfoGeoLocation alloc] init];

        element = [TBXML childElementNamed:@"name" parentElement:geoLocationElement];
        if (!element) {
            @throw [NSException exceptionWithName:@"ParseError" reason:@"No name in geolocation object." userInfo:nil];
        }
        igl.name = [[TBXML textForElement:element] unescapedFromXML];
        
        element = [TBXML childElementNamed:@"description" parentElement:geoLocationElement];
        if (!element) {
            @throw [NSException exceptionWithName:@"ParseError" reason:@"No description in geolocation object." userInfo:nil];
        }
		igl.description = [[TBXML textForElement:element] unescapedFromXML];
        
        element = [TBXML childElementNamed:@"street" parentElement:geoLocationElement];
        if (!element) {
            @throw [NSException exceptionWithName:@"ParseError" reason:@"No street in geolocation object." userInfo:nil];
        }
		igl.street = [[TBXML textForElement:element] unescapedFromXML];
        
        element = [TBXML childElementNamed:@"building" parentElement:geoLocationElement];
        if (!element) {
            @throw [NSException exceptionWithName:@"ParseError" reason:@"No building in geolocation object." userInfo:nil];
        }
		igl.building = [[TBXML textForElement:element] unescapedFromXML];
        
        element = [TBXML childElementNamed:@"postal" parentElement:geoLocationElement];
        if (!element) {
            @throw [NSException exceptionWithName:@"ParseError" reason:@"No postal in geolocation object." userInfo:nil];
        }
		igl.postal = [[TBXML textForElement:element] unescapedFromXML];
        
        element = [TBXML childElementNamed:@"city" parentElement:geoLocationElement];
        if (!element) {
            @throw [NSException exceptionWithName:@"ParseError" reason:@"No city in geolocation object." userInfo:nil];
        }
		igl.city =  [[TBXML textForElement:element] unescapedFromXML];
        
        element = [TBXML childElementNamed:@"lat" parentElement:geoLocationElement];
        if (!element) {
            @throw [NSException exceptionWithName:@"ParseError" reason:@"No latitude in geolocation object." userInfo:nil];
        }
		igl.latitude = [[TBXML textForElement:element] doubleValue];
        
        element = [TBXML childElementNamed:@"long" parentElement:geoLocationElement];
        if (!element) {
            @throw [NSException exceptionWithName:@"ParseError" reason:@"No longitude in geolocation object." userInfo:nil];
        }
		igl.longitude = [[TBXML textForElement:element] doubleValue];
        
		//DLog(@"Successfully decoded geolocation %@", igl.name);
		[geoLocations addObject:igl];
		geoLocationElement = geoLocationElement->nextSibling;
	}
	infoObject3.geoLocations = geoLocations;
	
	
	
	if (infoObjectType == INFOOBJECT_TYPE_ARRANGEMENT) {
		//parse calendar element
		
		//begin element
		infoObject3.beginDate = [InfoObject parseDate:[TBXML textForElement:beginElement]];
		
		//end element
		infoObject3.endDate = [InfoObject parseDate:[TBXML textForElement:endElement]];
		
		// locations
		TBXMLElement *locationElement = [TBXML childElementNamed:@"location" parentElement:locationsElement];
		NSMutableArray *locations = [[NSMutableArray alloc] init];
		while (locationElement) {
			TBXMLElement *nameElement = [TBXML childElementNamed:@"name" parentElement:locationElement];
			[locations addObject:[[TBXML textForElement:nameElement] unescapedFromXML]];
			locationElement = locationElement->nextSibling;
		}
		infoObject3.locations = locations;
		
		
	}
    
    if (infoObjectType == INFOOBJECT_TYPE_VOTE) {
        TBXMLElement *optionElement = [TBXML childElementNamed:@"option" parentElement:optionsElement];
        NSMutableArray *tmpArray = [NSMutableArray array];
        while (optionElement) {
            InfoVoteOption *optionObject = [[InfoVoteOption alloc] init];
            
            // id
            TBXMLElement *tmpElement = [TBXML childElementNamed:@"id" parentElement:optionElement];
            if (!tmpElement) {
                @throw [NSException exceptionWithName:@"ParseError" reason:@"No id in option object." userInfo:nil];
            }
            optionObject.infoObjectId = [[TBXML textForElement:tmpElement] unescapedFromXML];

            // value
            tmpElement = [TBXML childElementNamed:@"value" parentElement:optionElement];
            if (!tmpElement) {
                @throw [NSException exceptionWithName:@"ParseError" reason:@"No value in option object." userInfo:nil];
            }
            optionObject.value = [[TBXML textForElement:tmpElement] unescapedFromXML];
            
            // votes
            tmpElement = [TBXML childElementNamed:@"votes" parentElement:optionElement];
            if (!tmpElement) {
                @throw [NSException exceptionWithName:@"ParseError" reason:@"No votes in option object." userInfo:nil];
            }
            optionObject.votes = [[[TBXML textForElement:tmpElement] unescapedFromXML] integerValue];
            
            [tmpArray addObject:optionObject];
            
            optionElement = optionElement->nextSibling;
        }
        infoObject3.voteOptions = [NSArray arrayWithArray:tmpArray];
    }

	//DLog(@"Successfully parsed an InfoObject");
}

+ (NSDate*)parseDate:(NSString*)datestring
{
	//attempt on parsing date from string
	
	// Create date formatter
	NSDateFormatter *dateFormat_in = [[NSDateFormatter alloc] init];
	[dateFormat_in setLenient:TRUE];
	
	// Set the locale as needed in the formatter (this example uses Japanese)
	[dateFormat_in setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"da_DK"]];
	
	[dateFormat_in setDateFormat:@"yyyy-MM-dd HH:mm"];
	
	//DLog(@"in date: %@",datestring);
	NSDate* elementdate = [NSDate new]; // returned to caller who must retain this
	NSRange somerange;
	somerange.location=0;
	somerange.length=[datestring length];
	NSError* someerror = [NSError new];
	BOOL wentwell = [dateFormat_in getObjectValue:&elementdate forString:datestring range:&somerange error:&someerror];
	//NSLog(@"%d %d", somerange.location, somerange.length);
	
	if (!wentwell) {
		NSString* info = [someerror localizedDescription];
		ALog(@"error: %@",info);
	} else if (somerange.length!=[datestring length]) {
		ALog(@"Error: couldn't parse date string");
	}
	
	
	// We're done with this now
	
	NSDateFormatter *dateFormat_out = [[NSDateFormatter alloc] init];
	
	[dateFormat_out setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"da_DK"]];
	
	[dateFormat_out setDateFormat:@"dd MMM yyyy HH:mm"];
	
	// Create date string from formatter, using the current date
	// Display the date string in format we set above
	//DLog(@"Date: %@", [dateFormat_out stringFromDate:elementdate]);

	return elementdate;
}

- (NSString *)getHeadline:(NSString *)lang
{
	if ([self.translatedInfoObjects objectForKey:lang]) {
		InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:lang];
		return io.headline;
	} else {
		return nil;
	}
}

- (NSString *)getSubHeadline:(NSString *)lang
{
	if ([self.translatedInfoObjects objectForKey:lang]) {
		InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:lang];
		return io.subHeadline;
	} else {
		return nil;
	}
}

- (NSString *)getBody:(NSString *)lang
{
	if ([self.translatedInfoObjects objectForKey:lang]) {
		InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:lang];
		return io.body;
	} else {
		return nil;
	}
}

-(NSString*)getBodyWithConvertedPTags:(NSString *)lang
{
	if ([self.translatedInfoObjects objectForKey:lang]) {
		InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:lang];
        NSString* body = io.body;
        if (body!=nil) {
            body = [body stringByReplacingOccurrencesOfString:@"</p>" withString:@"&nbsp;</p>" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [body length])];
        }
		return body;
	} else {
		return nil;
	}
}

- (InfoObjectType)getType
{
	return infoObjectType;
}

- (NSArray *)getMedia:(NSString *)lang 
{
	if ([self.translatedInfoObjects objectForKey:lang]) {
		InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:lang];
		return io.multimedia;
	} else {
		return nil;
	}
}

- (InfoMedia *)getFirstVideoMedia:(NSString *)language
{
    for (InfoMedia* m in [self getMedia:language]) {
        if ( ! [[m mediaType] isEqualToString:@"video"]) {
            //DLog(@"skipping image element");
            continue;
        }
        
        NSString* mediaurl = [m sourceURL];
        if ([mediaurl length]==0) {
            //DLog(@"skipping image element with empty media url");
            continue;
        } else {
            //we found the first video - look no further
            return m;
        }
    }
    return nil;
    
}

- (InfoMedia *)getFirstImageMedia:(NSString *)language
{
    for (InfoMedia* m in [self getMedia:language]) {
        if ( ! [[m mediaType] isEqualToString:@"image"]) {
            //DLog(@"skipping video element");
            continue;
        }
        
        NSString* mediaurl = [m sourceURL];
        if ([mediaurl length]==0) {
            //DLog(@"skipping image element with empty media url");
            continue;
        } else {
            //we found the first image - look no further
            return m;
        }
    }
    return nil;
}

+(NSString *)getUrlForVideoMedia:(InfoMedia *)im withEncoding:(NSString *)enc
{
    if (im!=nil) {
        return [im getUrlForVideoWithEncoding:enc];
    }
    return nil;
}

- (NSArray *)getTags:(NSString *)lang
{
	if ([self.translatedInfoObjects objectForKey:lang]) {
		InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:lang];
		return io.tags;
	} else {
		return nil;
	}
}

- (NSArray *)getComments:(NSString *)lang
{
	if ([self->translatedInfoObjects objectForKey:lang]) {
		InfoObject3 *io = (InfoObject3 *)[self->translatedInfoObjects valueForKey:lang];
		return io.comments;
	} else {
		return nil;
	}
	
}

- (NSString *)getCategory:(NSString *)lang
{
	if ([self.translatedInfoObjects objectForKey:lang]) {
		InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:lang];
		return io.category;
	} else {
		return nil;
	}
}

- (NSArray *)getUrls:(NSString *)lang
{
	if ([self.translatedInfoObjects objectForKey:lang]) {
		InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:lang];
		return io.urls;
	} else {
		return nil;
	}
}

- (InfoEditor *)getEditor:(NSString *)lang
{
	if ([self.translatedInfoObjects objectForKey:lang]) {
		InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:lang];
		return io.editor;
	} else {
		return nil;
	}
}

- (InfoSource *)getSource:(NSString *)lang
{
	if ([self.translatedInfoObjects objectForKey:lang]) {
		InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:lang];
		return io.source;
	} else {
		return nil;
	}
}

- (NSDictionary *)getMetaData:(NSString *)lang
{
	if ([self.translatedInfoObjects objectForKey:lang]) {
		InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:lang];
		return io.metaData;
	} else {
		return nil;
	}
}

- (NSString*)getMetaData:(NSString *)lang forKey:(NSString*)key
{
	NSDictionary* metas = [self getMetaData:lang];
	if (metas!=nil) {
		return [metas objectForKey:key];
	} else {
		return nil;
	}
}


- (NSArray *)getLocations:(NSString *)lang
{
	if (infoObjectType != INFOOBJECT_TYPE_ARRANGEMENT) {
		[NSException raise:@"requested locations on non-arrangement element" format:@"Tried to inspect locations on a non-calendarelement (ie. arrangement) type element"];
	}
	if ([self.translatedInfoObjects objectForKey:lang]) {
		InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:lang];
		return io.locations;
	} else {
		return nil;
	}
}

- (NSArray *)getGeoLocations:(NSString *)lang
{
	if ([self.translatedInfoObjects objectForKey:lang]) {
		InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:lang];
		return io.geoLocations;
	} else {
		return nil;
	}
}

-(NSString *)getFirstLanguage
{
	NSArray* langs = [translatedInfoObjects allKeys];
	NSAssert([langs count]>0, @"no languages for infoobject");
	return [langs objectAtIndex:0];
}

- (NSArray *)getChildInfoObjects
{
	//we can assume that the child structure is the same for all languages
    NSString* anylang = [self getFirstLanguage];
	if (anylang==nil) {
		return nil;
	}
	if ([self.translatedInfoObjects objectForKey:anylang]) {
		InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:anylang];
		return io.childInfoObjects;
	} else {
		return nil;
	}
}

- (NSArray *)getChildInfoObjects:(NSString *)lang
{
	if ([self.translatedInfoObjects objectForKey:lang]) {
		InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:lang];
		return io.childInfoObjects;
	} else {
		return nil;
	}
}

- (NSString *)getParentInfoObject:(NSString *)lang
{
	if ([self.translatedInfoObjects objectForKey:lang]) {
		InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:lang];
		return io.parentInfoObject;
	} else {
		return nil;
	}
}

- (NSArray *)getChannels:(NSString *)lang
{
	if ([self.translatedInfoObjects objectForKey:lang]) {
		InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:lang];
		return io.channels;
	} else {
		return nil;
	}
}

- (NSDictionary *)getChannelIDs:(NSString *)lang
{
    if ([self.translatedInfoObjects objectForKey:lang]) {
        InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:lang];
        return io.channelIDs;
    } else {
        return nil;
    }
}

- (int)getProtocol
{
	return protocol;
}

- (NSDate*) getBeginDate:(NSString *)lang
{
	if (infoObjectType != INFOOBJECT_TYPE_ARRANGEMENT) {
		[NSException raise:@"requested begin date on non-arrangement element" format:@"Tried to inspect begin date on a non-calendarelement (ie. arrangement) type element"];
	}
	if ([self.translatedInfoObjects objectForKey:lang]) {
		InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:lang];
		return io.beginDate;
	} else {
		return nil;
	}
}

- (NSArray*) getVoteOptions:(NSString *)lang
{
    if (infoObjectType != INFOOBJECT_TYPE_VOTE) {
        [NSException raise:@"Requested vote options on non-vote element" format:@"Tried to get vote options on a non-voteelement type element."];
    }
	if ([self.translatedInfoObjects objectForKey:lang]) {
		InfoObject3 *io = (InfoObject3 *)[self.translatedInfoObjects valueForKey:lang];
		return io.voteOptions;
	} else {
		return nil;
	}    
}


+ (NSString*) unescapeString:(NSString*)string 
{
    return [string unescapedFromXML];
    /* moved to libutils
	if (string==nil) {
		return nil;
	}
	NSMutableString* unescaped = [NSMutableString stringWithString:string];
	[unescaped replaceOccurrencesOfString:@"&lt;" withString:@"<" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [unescaped length])];
	[unescaped replaceOccurrencesOfString:@"&gt;" withString:@">" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [unescaped length])];
	[unescaped replaceOccurrencesOfString:@"&quot;" withString:@"\"" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [unescaped length])];
	[unescaped replaceOccurrencesOfString:@"&amp;" withString:@"&" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [unescaped length])];
	[unescaped replaceOccurrencesOfString:@"&apos;" withString:@"'" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [unescaped length])];
	
	return unescaped;
     */
}


@end
